﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using WL.Core.Util;

namespace WL.Core.Licensing
{
    public class Amer
    {
        // 是否启用
        bool _enable = false;
        static Amer _singleton;
        static object _locker = new object();
        AmerLockInfo _lockInfo;
        string _lockFileName;
        string _pubKey = "<RSAKeyValue><Modulus>pjYga20BqcZfNMma2s7uFetN47/DLOGmGNKPKVikSrr3ajQ0wd6Bhk0Dut1ZGwfNy26+KGhNRTFbZGspsDtRUrdxPkc84kGEWiD3uvaA9mZ1o7iaGbuUfR2s8rstVoffA/CwZiuEy+QaQPW6qTZ11veF7fCr5IJYu+1udMbo1HU=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

        private Amer()
        {
            if (_enable)
            {
                Init();
            }
        }

        public static Amer Singleton
        {
            get
            {
                if (_singleton == null)
                {
                    lock (_locker)
                    {
                        if (_singleton == null)
                        {
                            _singleton = new Amer();
                        }
                    }
                }
                return _singleton;
            }
        }

        private void Init()
        {
            try
            {
                string enc = null;
                string token = null;
                var foldpath = System.AppDomain.CurrentDomain.BaseDirectory + "config";
                _lockFileName = foldpath + "\\.lock";
                if (!Directory.Exists(foldpath))
                {
                    Directory.CreateDirectory(foldpath);
                }

                if (File.Exists(_lockFileName))
                {
                    using (StreamReader streamReader = new StreamReader(_lockFileName, Encoding.UTF8))
                    {
                        enc = streamReader.ReadToEnd();
                    }
                }
                else
                {
                    token = MachineCode.Create();
                    enc = RegisterLock(token);
                }
                LoadLock(enc);
                if (string.IsNullOrEmpty(token))
                {
                    token = MachineCode.Create();
                }
                if (_lockInfo.Token != token)
                {
                    throw new InvalidOperationException($"{_lockInfo.Msg}：illegality =>  token");
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        private void Validate()
        {
            var now = DateTime.Now.AddMinutes(10);
            if (_lockInfo.Timestamp > now)
            {
                throw new InvalidOperationException($"{_lockInfo.Msg}：illegality => Timestamp：{_lockInfo.Timestamp:yyyy-MM-dd HH:mm:ss}");
            }
            if (_lockInfo.UpdateTime < now)
            {
                lock (_locker)
                {
                    if (_lockInfo.UpdateTime < now)
                    {
                        UpdateLock();
                    }
                }
            }
            if (_lockInfo.ExpireTime < now)
            {
                throw new InvalidOperationException($"{_lockInfo.Msg}：ExpireTime：{_lockInfo.ExpireTime:yyyy-MM-dd HH:mm:ss}");
            }
        }

        private void LoadLock(string enc)
        {
            if (string.IsNullOrEmpty(enc))
            {
                throw new InvalidOperationException($"Amer：empty lock");
            }
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(_pubKey);
                RSAParameters par = rsa.ExportParameters(false);
                var decStr = RSADecryptString(enc, new BigInteger(par.Exponent), new BigInteger(par.Modulus));
                rsa.Clear();
                _lockInfo = JsonConvert.DeserializeObject<AmerLockInfo>(decStr, new Newtonsoft.Json.Converters.IsoDateTimeConverter
                {
                    DateTimeFormat = "yyyy-MM-dd HH:mm:ss"
                });
            }
        }
        private void WriteLock(string enc)
        {
            if (string.IsNullOrEmpty(enc))
            {
                throw new InvalidOperationException($"Amer：empty lock");
            }
            using (StreamWriter streamWriter = new StreamWriter(_lockFileName, false, Encoding.UTF8, 1024))
            {
                streamWriter.Write(enc);
            }
        }

        private string RegisterLock(string token)
        {
            string enc = null;
            try
            {
                Uri url = new Uri("http://license.aimeier.top/api/amer/register");
                Dictionary<string, string> fields = new Dictionary<string, string> { { "token", token } };
                var result = HttpHelper.Get<ApiResult>(url, fields);
                if (result.Success)
                {
                    enc = result.Data;
                    WriteLock(enc);
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"Amer：register lock fail");
            }
            return enc;
        }

        private void UpdateLock()
        {
            try
            {
                Uri url = new Uri(_lockInfo.ApiUpdateUrl);
                Dictionary<string, string> fields = new Dictionary<string, string> { { "token", _lockInfo.Token } };
                var result = HttpHelper.Get<ApiResult>(url, fields);
                if (result.Success)
                {
                    WriteLock(result.Data);
                    LoadLock(result.Data);
                }
                //else
                //{
                //    throw new InvalidOperationException($"Amer：update error");
                //} 
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"Amer：update lock fail");
            }
        }

        public void Do()
        {
            if (!_enable)
            {
                return;
            }
            Validate();
            bool f = true;
            if (_lockInfo.CalcStyle == -1)
            {
                f = false;
            }
            else
            {
                var r = new Random().Next(_lockInfo.MaxRd);
                switch (_lockInfo.CalcStyle)
                {
                    case 0:
                        f = r < _lockInfo.Valve;
                        break;
                    case 1:
                        f = r == _lockInfo.Valve;
                        break;
                }
            }
            if (f)
            {
                throw new InvalidOperationException($"{_lockInfo.Msg}：rd error.");
            }
        }

        private String RSADecryptString(string source, BigInteger e, BigInteger n, Encoding encoding = null)
        {
            if (encoding == null)
                encoding = Encoding.UTF8;

            StringBuilder result = new StringBuilder();

            string[] strarr1 = source.Split(new char[] { '@' }, StringSplitOptions.RemoveEmptyEntries);

            for (int i = 0; i < strarr1.Length; i++)
            {
                string block = strarr1[i];
                BigInteger biText = new BigInteger(block, 16);
                BigInteger biEnText = biText.modPow(e, n);
                string temp = encoding.GetString(biEnText.getBytes());
                result.Append(temp);
            }

            return result.ToString();
        }
        class AmerLockInfo
        {
            public DateTime Timestamp { get; set; }
            public DateTime ExpireTime { get; set; }
            public DateTime UpdateTime { get; set; }
            public int MaxRd { get; set; }
            public int Valve { get; set; }
            public int CalcStyle { get; set; }
            public string Msg { get; set; }
            public string ApiUpdateUrl { get; set; }
            public string Token { get; set; }
        }

        class ApiResult
        {
            [JsonProperty("success")]
            public bool Success { get; set; }
            [JsonProperty("msg")]
            public string Msg { get; set; }
            [JsonProperty("data")]
            public string Data { get; set; }
        }
    }
}
